bsmdoc -- another technical html doc generator (ver 0.0.9) image/github.svg

1 What is bsmdoc?

bsmdoc is a tool to generate technical static html docs:

  • Light-weighted;
  • Highly extendable;
  • Single file doc: generate everything in a single file.

bsmdoc splits the whole doc into blocks (e.g., equation block, paragraph block, heading block, ...). In the following sections, we will show each block in detail, as well as the way to extend the existing blocks.

1.1 Installation

bsmdoc can be installed with pip

$ pip install bsmdoc

You can also clone the repository and run setup.py

$ pip install -e .

1.2 Get Started

  1. Create a file (e.g., helloworld.bsmdoc) with content

    = hello world
    
  2. Compile the file

    $ bsmdoc helloworld.bsmdoc
    
  3. It will generate helloworld.html in the same folder, which may look like

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="generator" content="bsmdoc 0.0.9">
    <link rel="stylesheet" href="css/bsmdoc.css" type="text/css">
    </head>
    <body class="nomathjax">
    <div class="layout">
    <div class="main">
    <div class="content">
    <h1>hello world</h1>
    </div>
    </div>
    <div class="footer">
    <div class="footer-text"> Last updated 2023-12-27 22:06:03 UTC by
    <a href="http://bsmdoc.feiyilin.com/">bsmdoc</a>.</div>
    </div>
    </div>
    </body>
    </html>
    

1.3 1-minute Tutorial

bsmdoc also provides some commands to create project and .bsmdoc.

  1. First, create a project

    $ mkdir myprj
    $ cd myprj
    $ bsmdoc init
    

    The last command will

      1. copy the default css and javascript files to the current folder;
      2. create index.bsmdoc from template;
      3. generate index.html from index.bsmdoc.
  2. Now, you can update the index.bsmdoc and then regenerate the html

    $ bsmdoc index.bsmdoc
    
  3. Add a new doc from template to current folder

    $ bsmdoc new page
    

    It will create the page.bsmdoc from template.

2 Heading

heading block is defined by a line starting with =

level 1

level 2

level 3

level 4

level 5
level 6
= level 1
== level 2
=== level 3
==== level 4
===== level 5
====== level 6

As you have seen, heading block must start at a new line. Otherwise, = (e.g., in a paragraph) is just normal equal sign. bsmdoc supports all 6 heading levels as shown above. However, we don't think anyone needs all these 6 levels (remember, "The Feynman Lectures on Physics" only uses 2 levels).

heading text can spread over multiple lines as long as they are enclosed by "{}", as shown in the following example. Actually, "{}" also define a basic block in bsmdoc. It is quite flexible to define the heading text; generally, you are allowed to put everything in it.

multiple
line
heading

multiple line heading

= {multiple\n
line\n
heading}
= {multiple
line
heading}

To add reference to the heading block, first you need to add a label with command "\label". Then, it can be referenced as a normal in-page link.

section with label

This section ...

= {section with label \label{sec-label}}
This [#sec-label|section] ...

By default, the heading block will not be automatically numbered. The following configuration is used to turn on the automatic numbering

\config{heading_numbering|True}

bsmdoc will not start the automatic heading numbering until you set the above flag. Thus if you want bsmdoc to add numbering to the whole doc, simply put the above configuration line at the beginning of the doc. You also can set the start heading level for automatic numbering. For example, the following line will tell bsmdoc to start heading numbering from level 2 (i.e., H2)

\config{heading_numbering_start|2}

When referencing to a heading block with automatic heading numbering on, the link text will be automatically filled with its index if it is empty,

Sec. 2 ...
Sec. [#sec-heading] ...

It can also be achieved by \ref command

Sec. 2...
Sec. \ref{sec-heading}...

bsmdoc will generate the table of contents from the headings, if the following configuration is enabled

\config{show_table_of_contents|True}

Only the headings with automatic numbering will be included in the content list. The table of contents is generated by a function block makecontent. Its input is a list that contains all the headings. The default implementation generates an un-ordered list

@BFunction('makecontent')
def bsmdoc_makecontent(contents, **kwargs):
    """
    table of contents is a list, each item
    [level, text, label]
        level: 1~6
        text: the caption text
        label: the anchor destination
    """
    if not contents:
        return ""
    first_level = min([c[0] for c in contents])
    call = []
    for c in contents:
        # the text has been parsed, so ignore the parsing here
        txt = BFunction().tag(c[1], 'a', 'href="#%s"' % c[2])
        call.append(['-' * (c[0] - first_level + 1), txt])
    return BFunction().listbullet(call)

3 List

bsmdoc defines two kinds of list blocks: unordered list and ordered list. A line leading with "-" will be rendered as unordered list. "-" will be ignored if it is not at the start of a line. Such rule holds for all heading, unordered list, and ordered list blocks.

  • start each line
  • with an hyphen -.
    • more asterisks gives deeper
      • and deeper levels.
  • line breaks
    don't break levels.
      • but jumping levels creates empty space.
any other start ends the list.
- start each line
- with an hyphen -.
-- more asterisks gives deeper
--- and deeper levels.
- line breaks\n don't break levels.
--- but jumping levels creates empty space.
any other start ends the list.

Ordered list starts with "*"

  1. start each line
  2. with a start '*'
    1. more asterisks gives deeper
      1. and deeper levels.
  3. line breaks
    don't break levels.
      1. but jumping levels creates empty space.
any other start ends the list.
* start each line
* with a start '*'
** more asterisks gives deeper
*** and deeper levels.
* line breaks\n don't break levels.
*** but jumping levels creates empty space.
any other start ends the list.

unordered and ordered lists can also be arbitrarily combined

  • unordered level 1
    1. ordered item 1
    2. ordered item 2
    • unordered item 3
    • unordered item 4
- unordered level 1
-* ordered item 1
-* ordered item 2
-- unordered item 3
-- unordered item 4

Each item of a list can spread over multiple lines as long as they are enclosed by "{}":

  • unordered level 1
    more text here bsmdoc
    1. ordered item2
- {unordered level 1\n
 more text here
[bsmdoc.feiyilin.com | bsmdoc]
}
-* ordered item2

4 Link

Text between [ and ] will be rendered as link block

[http://bsmedit.feiyilin.com]

Link text can be customized with "|"

[http://bsmdoc.feiyilin.com | bsmdoc]

Or email

[mailto:tq@feiyilin.com | Email]

You can also easily define in-page links with \anchor command

In page link
In page link \anchor{myanchor}

Then, you can link to the in-page link by

My in-page link
My in-page [#myanchor|link]

4.1 Footnote

One special in-page link is footnote. It will automatically add the link to the position it gets defined, and add the footnote content at the end of the page. bsmdoc also adds a shortcut at the end of the footnote content, so you can return to the footnote definition position easily.

footnote example1.
footnote example\footnote{This is a footnote}.

bsmdoc will automatically add indexing to each footnote. When you move the cursor to the footnote link, the footnote content will be shown in a popup window. So you may not need to go to the end of the page to see the footnote content. The image, equation and table blocks support similar feature.

4.2 Reference & Citation

bsmdoc has an easy way to add references and citations. For example, the following command will add a book reference

\reference{Simon|Simon Haykin, "Adaptive Filter Theory," Prentice Hall, 4th edition, Sep. 2001}

Simon before "|" is the alias, which can be used to cite it, e.g.,

The LMS algorithm in [1]...
The LMS algorithm in \cite{Simon}...

And the reference is not required to be defined before citation. If bsmdoc can not find the reference when it sees \cite, it will trigger the second scan, in case the reference is defined after the citation. If the reference is still missing at the second scan, bsmdoc will show a warning.

All cited references will be appended to the end of the document in the citation order. If you want to add the reference without citation, you can use the hide argument of the cite command

\cite{hide|Simon}...

In this case, it will not generate the link to the reference, but the reference will still be added to the reference list even if there is no explicit citation in the document.

5 Image & Video

5.1 Image

The syntax of the image block is

{!image||
image_file_path
!}

So, to include an image

./image/scatter.svg
{!image||
./image/scatter.svg
!}

You can add optional caption to an image block

./image/scatter.svg
Fig.2. Example scatter image
{!image||
\caption{Example scatter image}
./image/scatter.svg
!}

To add reference to an image, you need to

  1. add a label to an image block with \label command

    ./image/scatter.svg
    Fig.3. Example scatter image
    {!image||
    \label{img-scatter}
    \caption{Example scatter image}
    ./image/scatter.svg
    !}
    
  2. add reference to the image with \ref command

    Fig. 3 shows a scatter diagram
    Fig. \ref{img-scatter} shows a scatter diagram
    

Here the reference link \ref{img-scatter}) is defined after the definition of the image block. The reference link text is automatically replaced with the image index. In some case, if the reference link is created before the image block is defined, bsmdoc will not know the destination when it sees the reference link. In this case, the second scan will automatically be triggered to solve the reference link.

When the cursor is moved to the reference link, the referenced image will be highlighted if it is visible; otherwise, the image will be shown in a popup window. Such feature is inspired by "The Feynman Lectures on Physics" website. It allows you to view the images at the current reference position. Otherwise, you would have to follow the link to the original place where the image is first included. To use such feature, the image label should start with "img-", which is hard-coded in the Javascript. The references to equation, table, and footnote behave similarly.

As you have seen, the image block can automatically add the numbering to the image with label. The default automatic indexing format is: "Fig. I.", where "I" is the current index. You can configure the automatic prefix text. For example, to change it to "Image", the following line can be inserted before a image block definition (usually at the beginning of the doc, so that it affects all the image blocks)

\config{image_numbering_prefix|Image }

For example

./image/scatter.svg
Image 4. Example scatter image
{!image||
\config{image_numbering_prefix|Image }
\label{img-scatter2}
\caption{Example scatter image}
./image/scatter.svg
!}

The options to configure the image numbering

OptionDescription
image_numberingTrue or False (default). Turn on/off numbering, e.g.,
\config{image_numbering|True}
image_numbering_prefixThe numbering prefix (default "Fig."). To change the prefix, e.g.,
\config{image_numbering_prefix|Image.}
image_numbering_num_prefixThe numbering prefix (default ""). To change the prefix, e.g.,
\config{image_numbering_num_prefix|3.}

5.2 Video

Video block is almost same as the image block. Its syntax is

{!video||
video_file_path
!}

For example, to add reference to an image, you need to

  1. add a label to an image block with \label command

    Image 5. Example video
    {!video||
    \label{video-waves}
    \caption{Example video}
    ./image/waves.mp4
    !}
    
  2. add reference to the image with \ref command

    Vid. 5 shows a scatter diagram
    Vid. \ref{video-waves} shows a scatter diagram
    

The options to configure the video numbering

OptionDescription
video_numberingimage (default) or True or False. Turn on/off numbering. If it is set to image, then it will share numbering with image block.
video_numbering_prefixThe numbering prefix (default "Video."). Only valid if video_numbering is not image.
video_numbering_num_prefixThe numbering prefix (default ""). Only valid if video_numbering is not image.

6 Equation

Equation is rendered with mathjax: both inline and normal equation blocks. The configuration may look like:

<script>
    MathJax = {
        tex: {
            inlineMath: [['\\(', '\\)']],
            tags: "all"
        }
};
</script>

<script id="MathJax-script" async
src="https://cdn.jsdelivr.net/npm/mathjax@3.0.0/es5/tex-mml-chtml.js">
</script>

Such configuration can be included in a configuration file, which will be shown in detail in Sec. 12.

bsmdoc looks for "\(...\)" (or "$...$") as delimiters for inline equation block

Newton's second law is often stated as \(F=ma\), which means the force (\(F\)) acting on an object is equal to the mass (\(m\)) of an object times its acceleration (\(a\)).
Newton's second law is often stated as \(F=ma\), which means the force (\(F\)) acting on an object is equal to the mass ($m$) of an object times its acceleration ($a$).

The normal equation block looks like

{!math||{%
Latex Equation
%}!}

or

$$
Latex Equation
$$

For example

$$ \begin{align} \begin{bmatrix} 0 & \cdots & 0 \\ \vdots & \ddots & \vdots \\ 0 & \cdots & 0 \label{eqn:matrix} \end{bmatrix} \end{align} $$
{!math||{%
\begin{align}
\begin{bmatrix}
0      & \cdots & 0      \\
\vdots & \ddots & \vdots \\
0      & \cdots & 0
\label{eqn:matrix}
\end{bmatrix}
\end{align}
%}!}

bsmdoc generally keeps the equation content (i.e., the content between {%...%}) untouched, except < and >, which will be replaced with &lt; and &gt; respectively, to avoid conflicts with html tags. For example,

$a &gt; 5$
$a>5$

Unlike the image block mentioned above, the reference to equation is also handled by mathjax. The syntax is same as \(Latex\)

Eq. (\ref{eqn:matrix}) or Eq. (\ref{eqn:matrix})
Eq. (\ref{eqn:matrix}) or
Eq. (\eqref{eqn:matrix})

As mentioned above, same as the image block, when move the cursor to the equation reference link, bsmdoc will highlight the referenced equation if it is visible. Otherwise, a popup window will be displayed to show the equation.

7 Table

The table block is defined with {{...}}, where

  • each row is ended by "|-";
  • each column in a row is terminated by "|".

The content in each column of each row can be anything defined above, for example, Link, Image, Equation,...

item1\(E=MC^2\)
bsmdoc
image/scatter.svg
{{
item1 | $E=MC^2$ ||-
[http://bsmdoc.feiyilin.com|bsmdoc] | {!div|figure|image-left|image-thumbnail||\image{image/scatter.svg}!} ||-
}}

Optionally, a heading line can be added in the front of the table block

  • the heading line is delimited by "|+".

    Heading1Heading2
    item1item2
    item3item4
    {{
    Heading1 | Heading2||+
    item1 | item2 ||-
    item3 | item4 ||-
    }}
    

Furthermore, a caption can be added to the table block too

Example table title
Heading1Heading2
item1item2
item3item4
{{
\caption{Example table title}
Heading1 | Heading2||+
item1 | item2 ||-
item3 | item4 ||-
}}

bsmdoc can also add the automatic indexing to the table caption. To do that, you need to

  1. turn on the option

    \config{table_numbering|True}
    

    bsmdoc will add the automatic indexing to all the tables defined after the above line, until you explicitly turn off the configuration.

  2. add the label to each table if you want to reference it

    \label{table_label}
    

For example

Table.5. Example table title
Heading1Heading2
item1item2
item3item4
{{
\config{table_numbering|True}
\label{tbl-example}
\caption{Example table title}
Heading1 | Heading2||+
item1 | item2 ||-
item3 | item4 ||-
}}

The default automatic indexing format is: "Table. I.", where "I" is the current index. It can also be customized. For example, to change it to "TABLE", the following line can be inserted before the table block definition

\config{table_numbering_prefix|TABLE }
TABLE 6. Example table title
Heading1Heading2
item1item2
item3item4
{{
\config{table_numbering_prefix|TABLE }
\label{tbl-example2}
\caption{Example table title}
Heading1 | Heading2||+
item1 | item2 ||-
item3 | item4 ||-
}}

The label enables not only the automatic indexing, but also the cross-reference. Reference to a table is as easy as image and equation blocks

Table 5 shows how to make a table in bsmdoc.
Table \ref{tbl-example} shows how to make a table in bsmdoc.

If the table label text starts with "tbl-", when the cursor is moved to the reference index, the table will be highlighted if it is visible; otherwise, a popup window will be displayed to show the table content.

The options to configure the table numbering

OptionDescription
table_numberingTrue or False (default). Turn on/off numbering.
table_numbering_prefixThe numbering prefix (default "Table.").
table_numbering_num_prefixThe numbering prefix (default "").

8 Syntax Highlighting

bsmdoc uses Pygments for syntax highlighting. The syntax is

{!highlight|language||{%
code
%}!}

where language can be any language supported by Pygments. For example,

  • Python

    print("Hello World")
    
    {!highlight|python||{%
    print("Hello World")
    %}!}
    
  • Matlab

    fprintf(1, 'Hello, world!\n');
    
    {!highlight|matlab||{%
    fprintf(1, 'Hello, world!\n');
    %}!}
    
  • C++

    #include <iostream>
    using namespace std;
    
    int main ()
    {
        cout << "Hello World!";
        return 0;
    }
    
    {!highlight|C++||{%
    #include <iostream>
    using namespace std;
    
    int main ()
    {
        cout << "Hello World!";
        return 0;
    }
    %}!}
    

The highlight block supports a couple of options to format the code:

  • obeytabs: to replace tab with 4 space
  • gobble=N: remove the first N characters from each line.

    print("Hello World")
    
    {!highlight|python|gobble=4||{%
        print("Hello World")
    %}!}
    
  • autogobble: remove leading common white space from each line.
  • All other keyword arguments will be forwarded to HtmlFormatter. The following example turns on the line number, and highlights line 6.

    1
    2
    3
    4
    5
    6
    7
    8
    #include <iostream>
    using namespace std;
    
    int main ()
    {
        cout << "Hello World!";
        return 0;
    }
    
    {!highlight|C++|linenos=table|hl_lines=(6,)||{%
    #include <iostream>
    using namespace std;
    
    int main ()
    {
        cout << "Hello World!";
        return 0;
    }
    %}!}
    

It is also possible to use other packages for syntax highlighting, which will be discussed in detail in Sec. 10.

9 Raw Text Block

Sometimes you may want to skip the parsing from bsmdoc. For example, to highlight the code, it is basically not a good idea to let bsmdoc parse the code. Instead, the raw data should be sent to the code highlighting block. In another example, you may have seen similar syntax between the latex macro and the bsmdoc commands (e.g., both are preceded by "\"). If bsmdoc tries to parse these latex macros, the result is obviously not what you want. Thus, bsmdoc defines the raw text block. It basically tells bsmdoc to ignore all the rules on its content. The syntax of the raw text block is

{%
Content
%}

In this case, bsmdoc will stop parsing the Content between {%...%}, and the raw text will be either sent to the final html file or send to the other function blocks to process.

However, sometimes you may still want to slightly process the Content before sending to the html file. For example, the Content may contain symbols (e.g., < >), which may cause conflict to the html tags. You can use the escape function block to process the data

{!escape||{%
Content
%}!}

In this case, < and > will be replaced with &lt; and &gt;, respectively.

The equation block will automatically replace < (>) with &lt; (&gt;). Thus, there is no need to explicitly call the escape function block.

By symmetry, bsmdoc also defines the unescape function block, which will replace &lt; (&gt;) with < (>).

10 Function Block

Function block makes things really interesting. Without function block, bsmdoc is almost useless. Function block provides a easy way to allow the content to be processed before sent to the final html file. As you have seen above, the syntax of a function block looks like

{!command
content
!}

where command is optional and may contain the name and arguments of the corresponding function block.

As described above, many features mentioned above are implemented with function blocks. For example,

  • image block:

    {!image||
    image-path
    !}
    
  • equation block:

    {!math||{%
    latex-equation
    %}!}
    
  • syntax highlight block:

    {!highlight|language||{%
    code
    %}!}
    
  • div tag

    <div class="div-class">
    content
    </div>
    
    {!div|div-class||
    content
    !}
    
  • pre tag

    <pre>
    content
    </pre>
    
    {!pre||{%
    content
    %}!}
    
  • general tag

    <key class="tag-class">
    content
    </key>
    
    {!tag|key|tag-class||
    content
    !}
    

The command section is optional. When the command section is not defined, the function block behaves exactly same as the simple block ({}). Otherwise, the corresponding function will be called to process the content. As you may have already figured out, the command section is defined as

command|arg0|arg1|arg2|...|argN||

The command can have arbitrary number of arguments, which are separated by "|". And the command is always terminated by "||". It is also possible to combine multiple commands, e.g.,

command0|arg0|arg1|arg2||command1|arg0|arg1|arg2||

In this case, the commands are called from right to left. Thus in the above example, command1 will be called first, and its result will be sent to command0. The result from command0 will be sent to the html file. It is equivalently to the following nested blocks

{!command0|arg0|arg1|arg2||
{!command1|arg0|arg1|arg2||
    content
!}!}

The function block makes it easy to extend bsmdoc. We could have defined all kinds of function blocks to generate all html tags, and write a comprehensive doc so we can look up later. We don't. Otherwise, that will not be significantly different from remembering all html syntax. Instead, we make it easy to add new functions.

When bsmdoc sees the following function block

{!command|arg0|arg1||
    content
!}

it will search for the function decorated with "@BFunction('command')". If found, it will execute the command with list "[arg0, arg1]" as argument. For example, if you want to define a function block to achieve the "delete" effect,

  • first you need to define the function (e.g., "bsmdoc_del") with decorator "@BFunction('del')"; the function name (bsmdoc_del) is not important.

    @BFunction('del')
    def bsmdoc_del(data, *args, **kwargs):
        return "<del>%s</del>"%data
    

    *args will have all the arguments from the function block. For example, if del is called as

    {!del|arg0|arg1||
        content
    !}
    

    then bsmdoc will call bsmdoc_del by

    bsmdoc_del("content", "arg0", "arg1", **kwargs)
    

    Here **kwargs are additional arguments from bsmdoc, for example

    argumentdescription
    inlineTrue if it is a inline block (we will discuss the inline block shortly).
    filenamethe current filename
    linenothe line number of the start of the function block
    fun_argsthe list string arguments from function block
    fun_kwargsthe dict of keyword arguments from function block

    Such info may be helpful to show debugging tips once some error happens.

    bsmdoc will also parse the arguments from the function. All string arguments will be in list fun_args (e.g., 'args0') and keyword arguments will be in dict fun_kwargs (e.g., argument key=value will be convert to fun_kwargs[key]=value). In this case, if value is a number, it will be convert to a number, e.g., "answer=42" will be fun_kwargs['answer']=42; otherwise, value will be a string, e.g., "today=sunday" will be fun_kwargs['today']='sunday'.

    For a argument to be recognized as keyword argument, first, it has to have an "=" in it; second, the string to the left of the first "=" shall be a valid python identifier.

  • Now del (the name from @BFunction decorator) can be used as all the other blocks,

    <del>delete</del>
    
    {!del||
    delete
    !}
    

All command functions shall return a string, which will be sent to the next block or the html file. If the function does not need to change the html file, it shall return the empty string.

The next question is where to put the function definition? One straightforward solution is to define the above bsmdoc_del function in bsmdoc.py and then all the documents can use the del function block. However, that also means once you need some missing features, you need to go back to the bsmdoc.py and define the functions there. It may cause two potential issues:

  • It needs to edit additional file bsmdoc.py besides the docs you are working on. Thus the change is not local to the doc itself. If there is some mistake in the code, suddenly no doc can be compiled;
  • How about two docs need slightly different implementation of some function? You may end up either using some slightly different (but not straightforward) function names, or adding additional arguments to the function blocks.

Neither way is scalable. bsmdoc provides the third choice: the function block can be defined in the same doc where it is called. To achieve that, bsmdoc define a special exec block

{!exec||{%
code
%}!}

which will execute the code automatically. Thus, including the following code in your doc will automatically add the bsmdoc_del function just as it is defined in bsmdoc.py (but be careful, since bsmdoc will call python function "exec()" for this feature, it may cause some security concerns).

{!exec||{%
@BFunction('del')
def bsmdoc_del(data, *args, **kwargs):
    return "<del>%s</del>"%content
%}!}

With the exec block, you can replace all the predefined blocks with this method. For example, you can define your code highlight function block so that you can highlight your own language or with other library

{!exec||{%
@BFunction('highlight')
def bsmdoc_highlight(data, *args, **kwargs):
    ...
%}!}

Or in some case, you just want to use your own function for some arguments. For example, you want to call your own function to highlight the code for a specific language, and use the default function for all the others. In this case, you may try

{!exec||{%
bsmdoc_highlight_raw = BFunction().highlight # get the current highlight function
@BFunction('highlight')
def bsmdoc_highlight(data, *args, **kwargs):
    if args[0] != 'mylang':
        return bsmdoc_highlight_raw(data, *args, **kwargs)
    # highlight 'mylang'
    ...
%}!}

It may work fine to compile most of your docs, until one day it fails and python complains indefinite loop. The problem here is that the above definition assumes such code will only be called once. But it is not always true for bsmdoc. As mentioned before, bsmdoc may scan the doc multiple times to solve the late-defined references. Thus, when bsmdoc executes the above code second time, bsmdoc_highlight_raw will not refer to the original bsmdoc_highlight, instead, it will also point to the local defined copy. The definition will be equivalent to

{!exec||{%
bsmdoc_highlight_raw = BFunction().highlight
@BFunction('highlight')
def bsmdoc_highlight(data, *args, **kwargs):
    if args[0] != 'mylang':
        return bsmdoc_highlight(data, *args, **kwargs)
    # highlight 'mylang'
    ...
%}!}

It is apparently an indefinite loop.

Several techniques can be used to solve this problem. For example, you can always define the whole function by yourself, instead of calling the default function in some branches. Or you may check whether the local function has been defined or not

{!exec||{%
try:
    bsmdoc_highlight_raw
except NameError:
    bsmdoc_highlight_raw = BFunction().highlight
    @BFunction('highlight')
    def bsmdoc_highlight(data, *args, **kwargs):
        if args[0] != 'mylang':
            return bsmdoc_highlight(data, *args, **kwargs)
        # highlight 'mylang'
        ...
%}!}

Or you can tell bsmdoc to execute the code for the first scan only

{!exec|firstRunOnly||{%
bsmdoc_highlight_raw = BFunction().highlight
@BFunction('highlight')
def bsmdoc_highlight(data, *args, **kwargs):
    if lang != 'mylang':
        return bsmdoc_highlight(data, *args, **kwargs)
    # highlight 'mylang'
    ...
%}!}

10.1 Generate Images

Besides defining the function block, the exec block can also be used to execute arbitrary python code (be careful!). One application is to embed the python code to generate the figure with matplotlib package,

{!exec||{%
import os.path
if not os.path.isfile("pie.svg"):
    import matplotlib.pyplot as plt
    import numpy as np

    plt.clf()
    plt.figure(figsize=(4,4))
    # Compute pie slices
    N = 20
    theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False)
    radii = 10 * np.random.rand(N)
    width = np.pi / 4 * np.random.rand(N)

    ax = plt.subplot(111, projection='polar')
    bars = ax.bar(theta, radii, width=width, bottom=0.0)

    # Use custom colors and opacity
    for r, bar in zip(radii, bars):
        bar.set_facecolor(plt.cm.viridis(r / 10.))
        bar.set_alpha(0.5)
    plt.savefig("image/pie.svg")
%}!}

Then you can include the pie.svg in your html file

./image/pie.svg
{!image||
./image/pie.svg
!}

Thus, there may be no need to use other software to generate figures.

10.2 Include Source Code

With function block, it is easy to include source code in html doc. For example, to import the python source code in your doc, you can define the following function block

{!exec|firstRunOnly||{%
import inspect
import six
@BFunction('codesnippet')
def bsmdoc_codesnippet(data, *args, **kwargs):
    d = eval(data)
    if isinstance(d, six.string_types):
        return d
    else:
        return inspect.getsource(d)
%}!}

Then you can include the source code (e.g., matplotlib.pyplot.plot) by

@_copy_docstring_and_deprecators(Axes.plot)
def plot(
    *args: float | ArrayLike | str,
    scalex: bool = True,
    scaley: bool = True,
    data=None,
    **kwargs,
) -> list[Line2D]:
    return gca().plot(
        *args,
        scalex=scalex,
        scaley=scaley,
        **({"data": data} if data is not None else {}),
        **kwargs,
    )
{!exec|firstRunOnly||{%
import matplotlib.pyplot as plt
%}!}
{!highlight|python||codesnippet||
plt.plot
!}

It can be easily extended to include arbitrary code. For example, the following block returns the content of a source file. Of course, you can write a function block to just return certain sections (e.g., a function), instead of the whole file.

{!exec|firstRunOnly||{%
@BFunction('ccodesnippet')
def bsmdoc_ccodesnippet(data, *args, **kwargs):
    with open(data.strip(), 'r') as f:
        return ''.join(f.readlines())
    return data
%}!}

Then you can include the source code by

$(window).on("load", function() {
// Cache selectors
var lastId,
    topMenu = $(".menu"),
    topTitleHeight = $(".toptitle").outerHeight()+15,
    topUL = topMenu.find('ul'),
    // All list items
    menuItems = topMenu.find("a"),
    // Anchors corresponding to menu items
    scrollItems = menuItems.map(function(){
        var item = $($(this).attr("href"));
        if (item.length) { return item; }
    });

var auto_hide_child_menu = menuItems.length > 15;
// Bind click handler to menu items so we can get a fancy scroll animation
if (!auto_hide_child_menu) {
    menuItems.click(function(e){
        var href = $(this).attr("href"),
            offsetTop = href === "#" ? 0 : $(href).offset().top-15+1;
        $('html, body').stop().animate({
            scrollTop: offsetTop
        }, 300);
        e.preventDefault();
    });
}

function update_menu() {
    // Get container scroll position
    var fromTop = $(this).scrollTop() + 15;

    // Get id of current scroll item
    var cur = scrollItems.map(function(){
        if ($(this).offset().top < fromTop)
            return this;
    });
    // Get the id of the current element
    cur = cur[cur.length-1];
    var id = cur && cur.length ? cur[0].id : "";

    if (lastId !== id) {
        lastId = id;
        // Set/remove active class
        menuItems.removeClass("active");
        menuItems.filter("[href='#"+id+"']").addClass("active");
        if (auto_hide_child_menu) {
            topUL.find('ul').each(function(index){
                $(this).css('display', 'none');
            });
        }
        menu = menuItems.filter("[href='#"+id+"']");
        menuul = menu.closest('ul').css('display', 'inline-block');
        menu.closest('li').children('ul').css('display', 'inline-block');
    }
}

// Bind to scroll
$(window).scroll(function(){
    update_menu();
});

update_menu();
});
{!highlight|javascript||ccodesnippet||
js/menu.js
!}

10.3 Inline Function Block

The difference between function block and inline function block is similar to the equation block and inline equation block. In other words, the output of the inline block can be embedded in paragraphs. The syntax is similar to the \(\LaTeX\) macro:

\command{arg0|arg1|...|argN|content}

In general, the blocks defined in the above section can also be used as inline block. For example

<code>content</code>
<pre>content</pre>
\tag{code|content}
\pre{content}

bsmdoc defines a special inline function block for configuration (\config{}). It adds a configuration item to bsmdoc's global configuration table, and output empty string to the html file. Thus, it will not directly update the html docs, although the other blocks may rely on such configurations to control their behaviors. The syntax is

\config{option|value}}

We have seen heading, image, and table blocks use such feature to set the label and caption. We will see more configurations in Sec. 12.

10.4 Express Function Block

Besides the way mentioned above, there is a shortcut to define a function block, if it returns a simple string. The syntax to define a function is

\newfun{name|content}

For example

\newfun{bsmdoc|\tag{strong|bsmdoc}}

Once it is defined, it can be used as

bsmdoc
\bsmdoc

where \bsmdoc will be replaced with the result of \tag{strong|bsmdoc}, i.e.,

<strong>bsmdoc</strong>

11 Input Redirection

When the doc becomes large, you can split your doc into several docs and includes them in the top doc with the "#include" directive. For example, suppose you are writing a book, and have created chappter1.bsmdoc, chapter2.bsmdoc, .... Then, you can include all the chapters in your book.bsmdoc

#include chapter1.bsmdoc
#include chapter2.bsmdoc

bsmdoc will replace the line #include chapter1.bsmdoc with the content of the file chappter1.bsmdoc.

The #include is implemented with function bsmdoc_include

@BFunction('include')
def bsmdoc_include(data, **kwargs):
    filename = data.strip()
    if os.path.isfile(filename):
        return _bsmdoc_readfile(filename, **kwargs)
    else:
        _bsmdoc_error("can't not find %s" % filename, **kwargs)
    return ""

Here data will be anything following #include. In the previous example, it will be "chapter1.bsmdoc" or "chapter2.bsmdoc". If the file is opened successfully, by default bsmdoc_include will return the filename and its content.

It is easy to extend the bsmdoc_include function for more advanced applications, for example

{!exec|firstRunOnly||{%
bsmdoc_include_raw = BFunction().include
@BFunction('include')
def bsmdoc_include(data):
    # assume 'data' has multiple sections separated by '|', in the format of
    # PATTERN | MAX LINE | FILENAME
    d = data.strip().split('|')
    if len(d) == 1:
        # one section, return the default
        return bsmdoc_include_raw(data)
    elif len(d) == 3:
        import re
        # assume the last parameter is the filename
        c = bsmdoc_include_raw(d[-1])
        if not c:
            # invalid filename
            return c
        lines = c[1].split('\n')
        pattern = []
        for i, l in enumerate(lines):
            # search the PATTERN
            if re.match(d[0], l):
               pattern.append(i)
            if len(pattern) == 2:
                # return the content between the first and second instances of PATTERN
                if pattern[1] - pattern[0] > int(d[1]):
                    # too many lines, cut it
                    pattern[1] = pattern[0] + int(d[1])
                return (c[0], '\n'.join(lines[pattern[0]+1:pattern[1]]))
        return c
    return None
%}!}

Then the following code will include the content between the first two headings from file myfile.bsmdoc. And the maximum number of lines is limited to 16.

#include ^(\\s)*=+|16|./myfile.bsmdoc

12 HTML Template

By default, bsmdoc uses the following template to generate the html file, which defines 4 sections (i.e., html, header, body and footer)

[html]
begin = <!DOCTYPE html>
    <html>
end= </html>

[header]
begin = <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
end = </head>
content =
bsmdoc_css = ['css/bsmdoc.css']
bsmdoc_js = ['js/bsmdoc.js']
menu_css = ['css/menu.css']
menu_js = ['js/menu.js']
mathjax = <script>
            MathJax = {
                loader: {
                    load: ['[tex]/tagformat']
                },
                tex: {
                    packages: {'[+]': ['tagformat']},
                    inlineMath: [['\\(', '\\)']],
                    tags: "all",
                    tagformat: {
                        id: (id) => 'mjx-eqn-' + id.replace(/\s/g, '_'),
                    }
                }
            };
         </script>
         <script src=" https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-mml-chtml.min.js "></script>
jquery = <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>

[body]
begin = <body class="nomathjax">
        <div class="layout">
end = </div>
      </body>
# default content is
# %(article_menu)s
# <div class="main">
#     %(article_title)s
#     %(article_content)s
# </div>
content =

[footer]
begin = <div class="footer">
end = </div>
content = <div class="footer-text"> Last updated %(UPDATED)s by
          <a href="http://bsmdoc.feiyilin.com/">bsmdoc</a>%(SOURCE)s.</div>

Using a separate configuration file seems not to be compatible with our goal to include everything in a single file, although sometimes it may be convenient (for example, to use the same configuration file for multiple docs). bsmdoc also provides a way to include the customized template in your doc, for example

{!config||{%
[html]
begin = <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
end= </html>
...
%}!}

In this case, "config" function block is called without any argument. And its content should have a INI file structure.

bsmdoc uses ConfigParser to parse the configurations. Thus, %()s will be resolved to the corresponding configuration values. The configuration name is case insensitive. For example,

  • %(TITLE)s will be replaced with "my html title", if you config the title with

    \config{title|my html title}
    
  • %(UPDATED)s will be replaced with the time when the html file is generated.
  • %(SOURCE)s will be replaced with the bsmdoc filename.

It is easy to create a configuration by

  • define the macro content, e.g.,

    \config{MYMACRO|...}
    
  • use the configuration in the template

    ...%(MYMACRO}s...
    

bsmdoc defines "css" configuration to insert additional css in the html file without change the html template

\config{css|my.css}

To insert multiple css files, separate them with white-space

\config{css|my.css my2.css}

or

\config{css|my.css}
\config{css|add|my2.css}

where add option tells bsmdoc to append the configuration to the current one.

Similarly, bsmdoc also defines the "js" configuration to include javascripts in the html file

\config{js|myjs.js myjs2.js}

There are two ways to define doc title. The first one is to use the toptitle configuration. For example, the following line will define the top title

<div class="toptitle">
    bsmdoc -- another technical html doc generator
    <div class="subtitle">
        <a href='mailto:tq@feiyilin.com'>tq@feiyilin.com</a>
    </div>
</div>
\config{doctitle|bsmdoc -- another technical html doc generator}
\config{subtitle|[mailto:tq@feiyilin.com|tq@feiyilin.com]}

It is easy to see that it is equivalent to the following code

{!div|toptitle||
bsmdoc -- another technical html doc generator
{!div|subtitle||
[mailto:tq@feiyilin.com|tq@feiyilin.com]
!}
!}
  1. Simon Haykin, "Adaptive Filter Theory," Prentice Hall, 4th edition, Sep. 2001